package ch.areion.observator.server;

import ch.areion.common.message.Message;
import ch.areion.common.message.Module;
import ch.areion.common.message.Type;
import ch.areion.common.message.Version;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;

/**
 * Server class for catching data
 * @author Bruno Leupi
 */
public class LogServer implements Runnable, MessageListener {
	/**
	 * Port to listen
	 */
	private int port;
	/**
	 * Local Server Socket
	 */
	private ServerSocket service;

	/**
	 * Flag for closing streams, sockets and connections
	 */
	private boolean close = false;

	/**
	 * List with all listener
	 */
	private ArrayList<ServerListener> msgLister = new ArrayList<>();

	/**
	 * List with all connected clients.
	 */
	private ArrayList<ClientConnection> connections = new ArrayList<>();

	/**
	 * Constructor of the log-server
	 * @param port The port to listen to
	 */
	public LogServer(int port) {
		this.port = port;
	}

	/**
	 * Gets the state of the server
	 * @return Running status
	 */
	public boolean isRunning() {
		return !close;
	}

	/**
	 * Opens a socket, waits for a connection, creates buffers and starts
	 * listening
	 */
	@Override
	public void run() {
		if(openService()) {
			listen();
		}
		closeService();
	}

	/**
	 * Opens the socket.
	 * @return
	 */
	private boolean openService() {
		try {
			service = new ServerSocket(port);
		}
		catch(Exception e) {
			notifyMessageReceived(new Message(Version.UNDEFINED, Type.ERROR,
			    Module.COMMUNICATION, "Unable to open socket - " + e.getMessage()));
		}

		return service != null;
	}

	/**
	 * Waits for client connections
	 */
	private void listen() {
		while(!close) {
			Socket s;
			try {
				s = service.accept();
				ClientConnection con = new ClientConnection(s, this);
				notifyClientConnected(con.getAddress());
				new Thread(con).start();
				connections.add(con);
			}
			catch(IOException e) {
				notifyMessageReceived(new Message(Version.V01, Type.ERROR,
				    Module.COMMUNICATION, "Unable to connect client." + e.getMessage()));
			}
		}
	}

	/**
	 * Stops the server.
	 */
	public void stop_srv() {
		// close all client connections
		for(ClientConnection c : connections) {
			c.close();
		}

		// close socket
		closeService();
		close = true;
	}

	/**
	 * Closes the service
	 */
	private void closeService() {
		if(service != null) {
			try {
				service.close();
			}
			catch(IOException e) {
				notifyMessageReceived(new Message(Version.V01, Type.ERROR,
				    Module.COMMUNICATION, "Unable to close service - " + e.getMessage()));
			}
		}
	}

	/* Notification Methods */
	/**
	 * Notifies all listeners about a new message
	 * @param msg The received message
	 */
	private void notifyMessageReceived(Message msg) {
		for(ServerListener listener : msgLister) {
			if(listener != null) {
				listener.messageReceived(msg);
			}
		}
	}

	/**
	 * Notifies all listeners about a new client
	 * @param msg The received message
	 */
	private void notifyClientConnected(String client) {
		for(ServerListener listener : msgLister) {
			if(listener != null) {
				listener.clientConnected(client);
			}
		}
	}
	
	private void notifyClientDisconnected(ClientConnection con) {
		for(ServerListener listener : msgLister) {
			if(listener != null) {
				listener.clientDisconnected();
			}
		}
	}

	/**
	 * Registers a new listener
	 * @param listener The listener to add
	 */
	public void register(ServerListener listener) {
		msgLister.add(listener);
	}

	/**
	 * Removes an existing listener
	 * @param listener The listener to remove
	 */
	public void unregister(ServerListener listener) {
		if(msgLister.contains(listener))
			msgLister.remove(listener);
	}

	@Override
	public void messageReceived(Message msg) {
		if(msg != null) {
			notifyMessageReceived(msg);
		}
	}

	@Override
  public void clientDown(ClientConnection con) {
		connections.remove(con);
		notifyClientDisconnected(con);
  }
}